package model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Observable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import controller.LoaderCallable;
import util.*;

public class ContactsModel extends Observable {
	public static final String cvsSplitBy = ";";
	public static final String FILE_NAME = "contactBook.csv";

	private ArrayList<ContactModel> contacts;
	private ContactModel currentModel;
	private ComparatorAlpha comparatorAlpha;
	private ComparatorCreate comparatorCreate;
	private ComparatorGroup comparatorGroup;

	/**
	 * Initialize list
	 */
	public ContactsModel() {
		contacts = new ArrayList<ContactModel>();
		final long start = System.nanoTime();   
		//this.loadCsv();
		//this.initLoadDataCallables();
		//final long end = System.nanoTime();   
		//System.out.println((end - start ) / 1.0e9);
		comparatorAlpha = new ComparatorAlpha();
		comparatorCreate = new ComparatorCreate();
		comparatorGroup = new ComparatorGroup();
	}

	public void initLoadDataCallables() {
		int numberOfCores = Runtime.getRuntime().availableProcessors();
		double blockingCoefficient = 0.9;
		int poolSize = (int) (numberOfCores / (1 - blockingCoefficient));

		final List<Callable<ArrayList<ContactModel>>> partitions = new ArrayList<Callable<ArrayList<ContactModel>>>();
		final ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
		
		for(int i=0;i<poolSize; i++){
			LoaderCallable loaderCallable = new LoaderCallable();
			partitions.add(loaderCallable);
		}
		
		try {
			ArrayList<ContactModel> tempList = new ArrayList<ContactModel>();
			final List<Future<ArrayList<ContactModel>>> loaderResult = executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);	
			for(final Future<ArrayList<ContactModel>> contacts :  loaderResult ){
				tempList.addAll(contacts.get());
			}
			System.out.println(tempList.size());
			this.setContacts(tempList);
			LoaderCallable.closeBufferReader();
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

	/**
	 * Getter Model
	 * 
	 * @return
	 */
	public ArrayList<ContactModel> getContacts() {
		return contacts;
	}

	/**
	 * Setter Model
	 * 
	 * @param model
	 */
	public void setContacts(ArrayList<ContactModel> contacts) {
		this.contacts = contacts;
		setChanged();
		notifyObservers();
	}

	/**
	 * Getter CurrentModel
	 * 
	 * @return
	 */
	public ContactModel getCurrentModel() {
		return currentModel;
	}

	/**
	 * Setter CurrentModel
	 * 
	 * @param currentModel
	 */
	public void setCurrentModel(ContactModel currentModel) {
		this.currentModel = currentModel;
		setChanged();
		notifyObservers();
	}

	/**
	 * Get count of Contacts
	 * 
	 * @return
	 */
	public int getCount() {
		// System.out.println(this.contacts.size());
		return this.contacts.size();
	}

	/**
	 * Add a new contact
	 * 
	 * @param contactModel
	 */
	public void addContact(ContactModel contactModel) {
		this.contacts.add(contactModel);
		setChanged();
		notifyObservers();
	}

	/**
	 * Delete a contact
	 * 
	 * @param contactModel
	 */
	public void deleteContact(ContactModel contactModel) {
		if (this.contacts.contains(contactModel)) {
			this.contacts.remove(contactModel);
			setChanged();
			notifyObservers();
		}
	}

	/**
	 * Delete a contact
	 * 
	 * @param index
	 */
	public void deleteContact(int index) {
		if (index < this.contacts.size()) {
			this.contacts.remove(index);
			setChanged();
			notifyObservers();
		}
	}

	/**
	 * Update a contact
	 * 
	 * @param oldContactModel
	 * @param newContactModel
	 */
	public void updateContact(ContactModel oldContactModel,
			ContactModel newContactModel) {
		if (this.contacts.contains(oldContactModel)) {
			newContactModel.setId(oldContactModel.getId());
			int index = this.contacts.indexOf(oldContactModel);
			this.contacts.set(index, newContactModel);
			setChanged();
			notifyObservers();
		}
	}

	/**
	 * Sort contact by Alpha
	 */
	public void sortByAlpha() {
		Collections.sort(this.contacts, comparatorAlpha);
		setChanged();
		notifyObservers();
	}

	/**
	 * Sort contact by Group
	 */
	public void sortByGroup() {
		Collections.sort(this.contacts, comparatorGroup);
		setChanged();
		notifyObservers();
	}

	/**
	 * Sort contact by created time
	 */
	public void sortByCreate() {
		Collections.sort(this.contacts, comparatorCreate);
		setChanged();
		notifyObservers();
	}

	/**
	 * Read Data from csv file
	 * 
	 * @param path
	 *            : The path of the file
	 * @return whether load successfully
	 */
	private boolean loadCsv() {
		BufferedReader bufferReader = null;
		String line = "";
		if (fileCSVIsExist()) {
			try {
				bufferReader = new BufferedReader(new FileReader(FILE_NAME));
				while ((line = bufferReader.readLine()) != null) {
					// use comma as separator
					String[] contactItems = line.split(cvsSplitBy);
					ContactModel contactModel = new ContactModel();
					if (contactItems.length == contactModel.parametersCount) {
						contactModel.setFirstName(contactItems[0].trim());
						contactModel.setLastName(contactItems[1].trim());
						contactModel.setHomeAddress(contactItems[2].trim());
						contactModel.setCompanyAddress(contactItems[3].trim());
						contactModel.setPersonMobilePhone(contactItems[4]
								.trim());
						contactModel.setPersonHomePhone(contactItems[5].trim());
						contactModel.setProfessionalMobilePhone(contactItems[6]
								.trim());
						contactModel.setProfessionalOfficePhone(contactItems[7]
								.trim());
						contactModel.setHomePage(contactItems[8].trim());
						contactModel.setProfessionalEmail(contactItems[9]
								.trim());
						contactModel.setPersonalEmail(contactItems[10].trim());
						contactModel.setOther(contactItems[11].trim());
						contactModel.setImagePath(contactItems[12].trim());
						contactModel.setGroup(contactItems[13].trim());
						contactModel.setId(Integer.parseInt(contactItems[14]
								.trim()));
						addContact(contactModel);
					} else {
						bufferReader.close();
						return false;
					}
				}
				bufferReader.close();
				return true;
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				return false;
			} catch (IOException e) {
				e.printStackTrace();
				return false;
			}
		}
		return false;
	}

	/**
	 * Save data to csv file
	 */
	public void saveFile() {
		try {
			File file = new File(FILE_NAME);
			if (file.exists())
				file.delete();
			file.createNewFile();
			FileWriter fileWriter = new FileWriter(FILE_NAME, true);
			PrintWriter printWrite = new PrintWriter(fileWriter);
			// out put the header file
			this.sortByCreate();
			for (ContactModel contactModel : this.contacts) {
				String stringContact = contactModel.toString();
				printWrite.println(stringContact);
				printWrite.flush();
			}
			printWrite.close();
			fileWriter.close();

		} catch (IOException e) {
			System.out.println(" file path is not correct! current path./"
					+ FILE_NAME);
			e.printStackTrace();
		}
	}

	/**
	 * Check file is exist
	 * 
	 * @return
	 */
	private boolean fileCSVIsExist() {
		File file = new File(FILE_NAME);
		if (file.exists()) {
			return true;
		} else {
			return false;
		}

	}

}
